﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Common;

namespace DO.Workflows
{
    [DataContract]
    public class InstructionBase
    {
        [DataMember]
        public Guid ComponentID { get; set; }
        [DataMember]
        public Guid JobTicketID { get; set; }
        [DataMember]
        public DateTime SubmitTime { get; set; }

        #region ctor
        public InstructionBase()
            : this(Guid.NewGuid(), Guid.NewGuid())
        { }

        public InstructionBase(Guid componentID, Guid ticketID)
        {
            this.ComponentID = componentID;
            this.JobTicketID = ticketID;
            this.SubmitTime = DateTime.Now;
        }
        #endregion

        #region xml
        public virtual XElement Serialize()
        {
            XElement xe = new XElement(
                "Instruction",
                new XAttribute("ComponentID", this.ComponentID.ToString()),
                new XAttribute("JobID", this.JobTicketID.ToString()),
                new XAttribute("SubmitTime", this.SubmitTime.ToString()));
            return xe;
        }
        public virtual InstructionBase Instantiate(XElement xe)
        {
            InstructionBase instruction = (InstructionBase)Activator.CreateInstance(this.GetType());
            instruction.ComponentID = new Guid(xe.Attribute("ComponentID").Value);
            instruction.JobTicketID = new Guid(xe.Attribute("JobID").Value);
            instruction.SubmitTime = DateTime.Parse(xe.Attribute("SubmitTime").Value);
            return instruction;
        }
        #endregion

        #region bindings
        public virtual Dictionary<string, Type> GetBindableMemberNames(Type[] excludeMemberFromTypes)
        {
            Dictionary<string, Type> members = new Dictionary<string, Type>();
            FieldInfo[] fields = this.GetType().GetFields();
            if (fields != null && fields.Length > 0)
            {
                foreach (FieldInfo field in fields)
                {
                    members.Add(field.Name, field.FieldType);
                }
            }
            PropertyInfo[] props = this.GetType().GetProperties();
            if (props != null && props.Length > 0)
            {
                foreach (PropertyInfo prop in props)
                {
                    members.Add(prop.Name, prop.PropertyType);
                }
            }
            if (excludeMemberFromTypes != null && excludeMemberFromTypes.Length > 0)
            {
                foreach (Type exType in excludeMemberFromTypes)
                {
                    FieldInfo[] exFields = exType.GetFields();
                    if (exFields != null && exFields.Length > 0)
                    {
                        foreach (FieldInfo exField in exFields)
                        {
                            if (members.ContainsKey(exField.Name))
                                members.Remove(exField.Name);
                        }
                    }
                    PropertyInfo[] exProps = exType.GetProperties();
                    if (exProps != null && exProps.Length > 0)
                    {
                        foreach (PropertyInfo exProp in exProps)
                        {
                            if (members.ContainsKey(exProp.Name))
                                members.Remove(exProp.Name);
                        }
                    }
                }
            }

            return members;
        }

        /// <summary>provide a default behavior to read field value</summary>
        public virtual object ReadMemberValue(string memberName)
        {
            FieldInfo fieldInfo = this.GetType().GetField(memberName);
            if (fieldInfo != null)
            {
                return fieldInfo.GetValue(this);
            }
            else
            {
                PropertyInfo propInfo = this.GetType().GetProperty(memberName);
                if (propInfo != null)
                {
                    return propInfo.GetValue(this, null);
                }
            }
            return null;
        }

        /// <summary>provide a default behavior to set field value</summary>
        public virtual void SetMemberValue(string memberName, object memberValue)
        {
            MemberInfo[] members = this.GetType().GetMember(memberName);
            if (members != null && members.Length == 1)
            {
                MemberInfo member = members[0];
                if (member.MemberType == MemberTypes.Field)
                {
                    ((FieldInfo)member).SetValue(this, memberValue);
                }
                else if (member.MemberType == MemberTypes.Property)
                {
                    ((PropertyInfo)member).SetValue(this, memberValue, null);
                }
            }
        }
        #endregion

        #region clone
        public InstructionBase Clone()
        {
            InstructionBase newInstruction = (InstructionBase)Activator.CreateInstance(this.GetType());
            PropertyInfo[] props = this.GetType().GetProperties();
            foreach (PropertyInfo prop in props)
            {
                if (prop.CanRead && prop.CanWrite)
                {
                    object propValue = prop.GetValue(this, null);
                    prop.SetValue(newInstruction, propValue, null);
                }
            }

            return newInstruction;
        }
        #endregion

        #region global variable bindable
        /// <summary>
        /// 
        /// </summary>
        /// <param name="gvars"></param>
        public void BindToGlobalVariableValues(List<GlobalVariable> gvars)
        {
            if (gvars == null || gvars.Count == 0)
                return;
            Dictionary<string, Type> bindableMemberTypes =
                this.GetBindableMemberNames(new Type[] { typeof(InstructionBase) });
            if (bindableMemberTypes == null || bindableMemberTypes.Count == 0)
                return;
            Dictionary<string, GlobalVariable> globalVariables = new Dictionary<string, GlobalVariable>();
            foreach (GlobalVariable gVar in gvars)
            {
                if (!globalVariables.ContainsKey(gVar.VarName))
                    globalVariables.Add(gVar.VarName, gVar);
            }

            foreach (string memberName in bindableMemberTypes.Keys)
            {
                object memberValue = this.ReadMemberValue(memberName);
                if (memberValue == null)
                    continue;

                string memberValString = memberValue.ToString();
                string memberValReplaced = memberValString;
                Regex placeHolderRegex = new Regex(@"\$\{(\S+)\}");
                Match placeHolderMatch = placeHolderRegex.Match(memberValString);
                bool updateMemberValue = false;
                while (placeHolderMatch.Success)
                {
                    string placeHolder = placeHolderMatch.Groups[1].Value;
                    if (globalVariables.ContainsKey(placeHolder))
                    {
                        GlobalVariable gvar = globalVariables[placeHolder];
                        if (placeHolderMatch.Value.Length == memberValString.Length)
                        {
                            object gValue = SimpleDataTypeUtil.ToDbValue(gvar.DataType, gvar.VarValue);
                            this.SetMemberValue(memberName, gValue);
                            break;
                        }
                        else
                        {
                            updateMemberValue = true;
                            memberValReplaced = memberValReplaced.Replace(placeHolderMatch.Value, gvar.VarValue);
                        }
                    }

                    placeHolderMatch = placeHolderMatch.NextMatch();
                }

                if (updateMemberValue)
                {
                    this.SetMemberValue(memberName, memberValReplaced);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gvars"></param>
        public void ReplaceWithGlobalVariableBindings(List<GlobalVariable> gvars)
        {
            if (gvars == null || gvars.Count == 0)
                return;
            Dictionary<string, Type> bindableMemberTypes =
                this.GetBindableMemberNames(new Type[] { typeof(InstructionBase) });
            if (bindableMemberTypes == null || bindableMemberTypes.Count == 0)
                return;
            Dictionary<string, GlobalVariable> globalVariables = new Dictionary<string, GlobalVariable>();
            foreach (GlobalVariable gVar in gvars)
            {
                if (!string.IsNullOrEmpty(gVar.VarValue) && !globalVariables.ContainsKey(gVar.VarValue))
                    globalVariables.Add(gVar.VarValue, gVar);
            }

            foreach (string memberName in bindableMemberTypes.Keys)
            {
                object memberValue = this.ReadMemberValue(memberName);
                if (memberValue == null)
                    continue;

                string memberValString = memberValue.ToString();
                if (globalVariables.ContainsKey(memberValString))
                {
                    GlobalVariable gvar = globalVariables[memberValString];
                    if (gvar.DataType == SimpleDataType.TextType)
                    {
                        string placeHolder = "${" + gvar.VarName + "}";
                        this.SetMemberValue(memberName, placeHolder);
                    }
                }
            }
        }
        #endregion
    }
}
